use std::borrow::Cow;

use thiserror::Error;

pub type IoErr = std::io::Error;
pub type SqlxErr = sqlx::Error;

#[derive(Debug, Error)]
pub enum AppErr {
    #[error("io:{0}")]
    Io(#[from] IoErr),

    #[error("sqlx:{0}")]
    Sqlx(#[from] SqlxErr),

    #[error("{0}")]
    Custom(Cow<'static, str>),

    #[error("{0} => {1}")]
    Service(i32, Cow<'static, str>),
}

impl AppErr {

    pub fn new_service(ec: i32, msg: &'static str) -> Self {
        Self::Service(ec, Cow::Borrowed(msg))
    }
}

pub const EC_NO_DEVICE: i32 = 1;
pub const EC_NO_LOGIN: i32 = 2;
pub const EC_TOKEN_INVALID: i32 = 3;
pub const EC_RX_WAIT_TIMEOUT: i32 = 4;
pub const EC_RECV_TIMEOUT: i32 = 5;

pub trait UniqueMap<T> {
    fn map_unique(self, msg: &'static str) -> Result<T, AppErr>;
}

impl<T> UniqueMap<T> for Result<T, SqlxErr> {
    fn map_unique(self, msg: &'static str) -> Result<T, AppErr> {
        match self {
            Ok(v) => Ok(v),
            Err(e) => {
                if let Some(err) = e.as_database_error() {
                    if err.is_unique_violation() {
                        Err(AppErr::Custom(Cow::Borrowed(msg)))
                    } else {
                        Err(e.into())
                    }
                } else {
                    Err(e.into())
                }
            }
        }
    }
}

impl AppErr {
    pub fn from_static(msg: &'static str) -> Self {
        Self::Custom(Cow::Borrowed(msg))
    }

    // pub fn from_string(msg: String) -> Self {
    //     Self::Custom(Cow::Owned(msg))
    // }
}
